﻿using System;
using System.Linq;
using EPiServer;
using EPiServer.Core;
using PageTypeBuilder;
using TemplateFoundation.Core;
using TemplateFoundation.Tabs;
using log4net;

namespace TemplateFoundation.PageTypes
{
    /// <summary>
    /// Base class for date folder page types, which in turn are commonly used to list all articles for a specific year or month
    /// </summary>
    public abstract class ArticleDateListingPageBase : ArticleListingPageBase
    {
        private static readonly ILog _logger = LogManager.GetLogger(typeof (ArticleDateListingPageBase));

        private object _articleDateListingType; // Non-typed to allow null until Type property is lazy loaded

        /// <summary>
        /// Gets the articles to list if this is a year or month container
        /// </summary>
        public override PageDataCollection Articles
        {
            get
            {
                DetermineDateRangeBoundaries();

                switch (Type)
                {
                    case ArticleDateListingType.Year:
                        return PageFactory.Instance.GetArticlesByYear(int.Parse(PageName));
                    case ArticleDateListingType.Month:
                        return PageFactory.Instance.GetArticlesByMonth(int.Parse(ParentLink.GetPage().PageName), int.Parse(PageName));
                    case ArticleDateListingType.Day:
                        return new PageDataCollection(
                                   PageFactory.Instance.GetArticlesByMonth(
                                        int.Parse(ParentLink.GetPage().ParentLink.GetPage().PageName), // Year
                                        int.Parse(ParentLink.GetPage().PageName)) // Month
                                   .Where(a => a.StartPublish.Day == int.Parse(PageName))); // Day
                    default:
                        return new PageDataCollection(0);

                }
            }
        }

        /// <summary>
        /// Determines the date range if the listing is for a specific year, month, or day
        /// </summary>
        private void DetermineDateRangeBoundaries()
        {
            int fromYear = DateTime.MinValue.Year, fromMonth = 1, fromDay = 1;
            int toYear = DateTime.MaxValue.Year, toMonth = 12, toDay = 31;

            try
            {
                // Determine year, month, and day of the lower and upper date boundaries
                switch (Type)
                {
                    case ArticleDateListingType.Year:
                        fromYear = int.Parse(PageName);
                        toYear = fromYear;
                        break;
                    case ArticleDateListingType.Month:
                        fromYear = int.Parse(ParentLink.GetPage().PageName);
                        fromMonth = int.Parse(PageName);
                        toMonth = fromMonth;
                        break;
                    case ArticleDateListingType.Day:
                        fromYear = int.Parse(ParentLink.GetPage().ParentLink.GetPage().PageName);
                        toYear = fromYear;
                        fromMonth = int.Parse(ParentLink.GetPage().PageName);
                        toMonth = fromMonth;
                        fromDay = int.Parse(PageName);
                        toDay = fromDay;
                        break;
                }

                // Set date boundary properties
                From = new DateTime(fromYear, fromMonth, fromDay);
                To = new DateTime(toYear, toMonth, toDay);
            }
            catch (Exception ex)
            {
                _logger.ErrorFormat("[ETF] Unable to determine article list date range boundaries: {0}", ex.Message);
            }
        }

        /// <summary>
        /// Gets the type of listing, for example for a year or month container page
        /// </summary>
        public ArticleDateListingType Type
        {
            get
            {
                if (_articleDateListingType == null)
                {
                    _articleDateListingType = DetermineArticleDateListingType();
                }

                return (ArticleDateListingType)_articleDateListingType;
            }
        }

        private ArticleDateListingType DetermineArticleDateListingType()
        {
            if (PageName.Length == 4) // Considered a year container if page name is a 4-digit number
            {
                int year;

                return int.TryParse(PageName, out year)
                           ? ArticleDateListingType.Year
                           : ArticleDateListingType.None;
            }

            if (PageName.Length <= 2) // Considered month container if page name is a 1 or 2-digit number and parent is a year container
            {
                var parentIsYearContainer = ParentLink.GetPage() is ArticleDateListingPageBase &&
                                            ((ArticleDateListingPageBase) ParentLink.GetPage()).Type ==
                                            ArticleDateListingType.Year;

                if (parentIsYearContainer)
                {
                    int month;

                    var isNumber = int.TryParse(PageName, out month);

                    if (isNumber && month > 0 && month <= 12)
                    {
                        return ArticleDateListingType.Month;
                    }

                    return ArticleDateListingType.None;
                }
            }

            if (PageName.Length <= 2) // Considered day container if page name is a 1 or 2-digit number and parent is a month container
            {
                var parentIsMonthContainer = ParentLink.GetPage() is ArticleDateListingPageBase &&
                                             ((ArticleDateListingPageBase) ParentLink.GetPage()).Type ==
                                             ArticleDateListingType.Month;

                if (parentIsMonthContainer)
                {
                    int day;

                    var isNumber = int.TryParse(PageName, out day);

                    if (isNumber && day > 0 && day <= 31) // TODO Validate day against parent's month
                    {
                        return ArticleDateListingType.Day;
                    }

                    return ArticleDateListingType.None;
                }
            }

            return ArticleDateListingType.None;
        }

        /// <summary>
        /// Gets or sets the title of the listing page
        /// </summary>
        /// <remarks>If title is empty, or equal to page name, the title will be the year or month name for the listing</remarks>
        [PageTypeProperty(
            EditCaption = "List title",
            HelpText = "If title is empty, or equal to page name, the title will be the year or month name for the listing",
            UniqueValuePerLanguage = true,
            Tab = typeof(SeoTab),
            Type = typeof(PropertyString))]
        public override string Title
        {
            get
            {
                // Check if the title has been explicitly set
                string title = this.GetPropertyValue(page => page.Title);

                if (!string.IsNullOrEmpty(title) && title != PageName)
                {
                    return title;
                }

                // Check that the title can be parsed to an integer
                int titleAsInteger;

                if (!int.TryParse(title, out titleAsInteger))
                {
                    return title;
                }


                switch(Type)
                {
                    case ArticleDateListingType.Month: // Returns name of the month
                        return new DateTime(DateTime.Now.Year, titleAsInteger, 1).ToString("MMMM");
                    default:
                        return PageName;
                }
            }
        }

        /// <summary>
        /// Gets the lower boundary of the article date range
        /// </summary>
        /// <remarks>Determined by the date folder structure</remarks>
        public DateTime From { get; protected set; }

        /// <summary>
        /// Gets the upper boundary of the article date range
        /// </summary>
        /// <remarks>Determined by the date folder structure</remarks>
        public DateTime To { get; protected set; }
    }
}